package org.scalpel.common;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Jerome Monceaux
 * @version 1.0
 * 
 * This class help to introspect a class in order to find 
 * a method that is compatible to a given method signature.
 * It is used to dynamically connect input (method) and output (event to observer) 
 */
@SuppressWarnings("rawtypes")
public class ReflectionUtil {
  
  private static Map<Class,Class> primitiveMap = new HashMap<Class,Class>();
  static {
    primitiveMap.put(double.class, Double.class);
    primitiveMap.put(int.class, Integer.class);
    primitiveMap.put(float.class, Float.class);
    primitiveMap.put(short.class, Short.class);
    primitiveMap.put(long.class, Long.class);
    primitiveMap.put(boolean.class, Boolean.class);
    primitiveMap.put(byte.class, Byte.class);
    primitiveMap.put(char.class, Character.class);
  }

  public static Constructor getCompatibleConstructor(Class c, 
       Class... paramTypes) {

    Constructor[] contructors = c.getConstructors();
    for (int i = 0; i < contructors.length; i++) {
      Constructor m = contructors[i];

      Class<?>[] actualTypes = m.getParameterTypes();
      if( actualTypes == null && paramTypes == null)
        return m;
      
      if ((paramTypes != null && actualTypes !=null) && actualTypes.length != paramTypes.length) {
        continue;
      }

      boolean found = true;
      for (int j = 0; j < actualTypes.length; j++) {
        if (!actualTypes[j].isAssignableFrom(paramTypes[j])) { 
          if (actualTypes[j].isPrimitive()) {
            found = primitiveMap.get(actualTypes[j]).equals(paramTypes[j]);
          }
          else if (paramTypes[j].isPrimitive()) {
            found = primitiveMap.get(paramTypes[j]).equals(actualTypes[j]);
          }
        }
        
        if (!found) {
          break;
        }
      }

      if (found) {
        return m;
      }
    }

    return null;  
  }
  
  public static Method getCompatibleMethod(Class c, 
             String methodName, 
             Class... paramTypes) {
    
    Method[] methods = c.getMethods();
    for (int i = 0; i < methods.length; i++) {
      Method m = methods[i];

      if (!m.getName().equals(methodName)) {
        continue;
      }

      Class<?>[] actualTypes = m.getParameterTypes();
      if( actualTypes == null && paramTypes == null)
        return m;
      
      if ((paramTypes != null && actualTypes !=null) && actualTypes.length != paramTypes.length) {
        continue;
      }

      boolean found = true;
      for (int j = 0; j < actualTypes.length; j++) {
        if (!actualTypes[j].isAssignableFrom(paramTypes[j])) { 
          if (actualTypes[j].isPrimitive()) {
            found = primitiveMap.get(actualTypes[j]).equals(paramTypes[j]);
          }
          else if (paramTypes[j].isPrimitive()) {
            found = primitiveMap.get(paramTypes[j]).equals(actualTypes[j]);
          }
        }
        
        if (!found) {
          break;
        }
      }

      if (found) {
        return m;
      }
    }

    return null;
  }
}